/*
 * XML.cpp
 *
 *  Created on: Jan 15, 2011
 *      Author: LyonsDesktop
 */

#include <algorithm>
#include <iostream>
#include <fstream>

#include "XML.h"

#ifdef FRAMEWORKMEMORYTRACKER
#include "Extern.h"
#define new new(__FILE__, __LINE__)
#endif

using namespace XML;
using namespace std;

bool IsWhiteSpace(char c)
{
	if (c == ' ' || c == '\r' || c == '\n' || c == '\t') { return true; }
	return false;
}

void Trim(string& s)
{
	while (true)
	{
		size_t slen = s.length();
		std::remove(s.begin(), s.begin() + 1, ' ');
		std::remove(s.begin(), s.begin() + 1, '\r');
		std::remove(s.begin(), s.begin() + 1, '\n');
		std::remove(s.begin(), s.begin() + 1, '\t');
		std::remove(s.end() - 1, s.end(), ' ');
		std::remove(s.end() - 1, s.end(), '\r');
		std::remove(s.end() - 1, s.end(), '\n');
		std::remove(s.end() - 1, s.end(), '\t');
		if (s.length() == slen) { break; }
	}
}

XML::Element* ReadTagName(string text, int pos, bool Full)
{
	if (pos >= (int)text.length() || text[pos] != '<') { return 0; }
	Element* t = new Element();
	t->StartPos = pos;

	pos++;
	while (pos < (int)text.length() && IsWhiteSpace(text[pos])) { ++pos; }
	string tag = "";
	if (pos < (int)text.length() && (text[pos] == '/' || text[pos] == '\\'))
	{
		tag += '/';
		++pos;
	}

	while (pos < (int)text.length())
	{
		char c = text[pos];
		if (IsWhiteSpace(c)) { break; }
		if (c == '/' || c == '\\' || c == '>') { break; }
		tag += c;
		++pos;
	}
	t->EndPos = -1;

	if (pos < (int)text.length()) { t->EndPos = text.find('>', pos); }
	if (t->EndPos == -1) { t->EndPos = pos; }
	else
	{
		t->EndPos++;
	}

	if (Full)
	{
		bool InText = false;
		bool InWithoutQuotes = false;
		bool HadEqual = false;
		Element::Parameter elem;
		while (pos < (int)text.length() && IsWhiteSpace(text[pos])) { pos++; }
		for (int i = 0; i < t->EndPos - pos - 1; i++)
		{
			int offset = pos + i;
			if (InText && InWithoutQuotes && IsWhiteSpace(text[offset]))
			{
				InText = InWithoutQuotes = false;
				t->Parameters.push_back(elem); elem.Name = elem.Value = "";
				HadEqual = false;
			}
			else if (text[offset] == '>')
			{
				break;
			}
			else if (InText && (text[offset] == '\"' || text[offset] == '\''))
			{
				InText = !InText;
				if (!InText) { t->Parameters.push_back(elem); elem.Name = elem.Value = ""; HadEqual = false; }
			}
			else if (!InText && text[offset] == '=')
			{
				HadEqual = true;
				InText = true;
				if (offset + 1 < (int)text.length() && (text[offset + 1] == '\"' || text[offset + 1] == '\''))
				{ ++i; InWithoutQuotes = false; }
				else { InWithoutQuotes = true; }
				continue;
			}
			else if (!InText)
			{
				if (IsWhiteSpace(text[offset]) && HadEqual)
				{ t->Parameters.push_back(elem); elem.Name = elem.Value = ""; HadEqual = false; continue; }
				else
				{
					elem.Name += text[offset];
				}
			}
			else
			{
				elem.Value += text[offset];
			}
		}
		if (elem.Name != "" && elem.Value != "")
		{
			t->Parameters.push_back(elem);
		}

		for (unsigned int i = 0; i < t->Parameters.size(); i++)
		{
			Trim(t->Parameters[i].Name);
			Trim(t->Parameters[i].Value);
			if (t->Parameters[i].Name == "" && t->Parameters[i].Value == "")
			{
				t->Parameters.erase(t->Parameters.begin() + i--);
				continue;
			}
			if (t->Parameters[i].Name == "\\" || t->Parameters[i].Name == "/")
			{
				if (t->Parameters[i].Value == "")
				{
					t->Parameters.erase(t->Parameters.begin() + i--);
					continue;
				}
			}
		}
	}

	t->Name = tag;
	if (t->EndPos > t->StartPos) { t->Source = text.substr(t->StartPos, t->EndPos - t->StartPos); }
	else { t->Source = ""; }

	return t;
}

string ReadToMatchingTag(XML::Element* tag, string Body, int pos, int& endpos)
{
	if (tag->Source[tag->Source.length() - 2] == '/')
	{
		endpos = tag->EndPos;
		return "";
	}

	if (tag)
	{
		int depth = 1;
		string end_tag = '/' + tag->Name;
		int next_index = Body.find('<', pos + 1);
		int end_index = Body.find('>', pos + 1);
		while (end_index > -1 && next_index > -1 && next_index < (int)Body.length() - 1)
		{
			XML::Element* rt = ReadTagName(Body, next_index, false);
#ifdef FRAMEWORKMEMORYTRACER
			MemoryTracker::TraceTo(rt, __FILE__, __LINE__);
#endif

			if (rt->Name == tag->Name) { depth++; }
			if (rt->Name == end_tag) { depth--; }

			if (depth == 0)
			{
				string substr = Body.substr(end_index + 1, next_index - end_index - 1);
				endpos = rt->EndPos;
				delete rt;
				return substr;
			}
			next_index = Body.find('<', next_index + 1);

			delete rt;
		}
	}
	endpos = -1;
	return "";
}

void Element::WriteTo(std::ostream& o, int depth)
{
	for (int i = 0; i < depth; i++) { o << '\t'; }
	o << Name;
	o << '(';
	for (unsigned int i = 0; i < Parameters.size(); i++)
	{
		if (i > 0) { o << ","; }
		o << Parameters[i].Name + "=" + Parameters[i].Value;
	}
	o << ')';
	o << endl;
	for (unsigned int i = 0; i < Nodes.size(); i++)
	{
		Nodes[i]->WriteTo(o, depth + 1);
	}
}

Body::Body(string& Text)
{
	vector<Element*> NodeQueue;
	root = new Element();
	root->Parent = 0;
	root->Source = root->Subtext = Text;

	NodeQueue.push_back(root);
	while (!NodeQueue.empty())
	{
		Element* BodyNode = NodeQueue[0];
		string Source = BodyNode->Subtext;
		NodeQueue.erase(NodeQueue.begin());

		if (Source == "") { continue; }
		int first_tag = Source.find('<');
		while (first_tag > -1)
		{
			Element* tag = ReadTagName(Source, first_tag, true);
/*
#ifdef FRAMEWORKMEMORYTRACER
			MemoryTracker::TraceTo(tag, __FILE__, __LINE__);
#endif
*/

			if (tag && tag->Name[0] != '/' && tag->Name[0] != '!')
			{
				int endpos = 0;
				string tagbody = ReadToMatchingTag(tag, Source, first_tag, endpos);

				if ((tagbody != "" && endpos > first_tag)
					||
					((endpos = tag->EndPos) > first_tag))
				{
					string s1 = "";
					if (first_tag > 0) { s1 = Source.substr(0, first_tag); }
					string s2 = "";
					if (endpos < (int)Source.length()) { s2 = Source.substr(endpos, Source.length() - endpos); }
					Source = s1 + s2;
					first_tag = -1;
				}
				tag->Subtext = tagbody;
				tag->Parent = BodyNode;

/*
#ifdef FRAMEWORKMEMORYTRACER
				MemoryTracker::TraceTo(tag, __FILE__, __LINE__);
#endif
*/

				BodyNode->Nodes.push_back(tag);
				NodeQueue.push_back(tag);
			}
			else
			{
				delete tag;
			}
			if (Source != "")
			{
				if (first_tag > (int)Source.length()) { first_tag = -1; }
				first_tag = Source.find('<', first_tag + 1);
			}
			else { first_tag = -1; }
		}
	}

	root->Subtext = "";
}

Body::~Body()
{
	if (root) { delete root; }
	root = 0;
}

Element::~Element()
{
	for (unsigned int i = 0; i < Nodes.size(); i++)
	{
		delete Nodes[i];
	}
	Nodes.clear();
}

Element* Element::GetElement(std::string Name, Element* cur, bool Recurse)
{
	bool hitcur = false;
	for (unsigned int i = 0; i < Nodes.size(); i++)
	{
		Element* e = Nodes[i];
		if (e == cur) { hitcur = true; continue; }
		if (cur && !hitcur) { continue; }

		if (e->Name == Name) { return e; }
		if (Recurse && (e = e->GetElement(Name)))
		{
			return e;
		}
	}
	return 0;
}

Element::Parameter* Element::GetParameter(std::string Name)
{
	for (unsigned int i = 0; i < Parameters.size(); i++)
	{
		if (Parameters[i].Name == Name) { return &Parameters[i]; }
	}
	return 0;
}

//void Body::ToFile(const char* filename)
//{
//	if (root)
//	{
//		std::ofstream outfile(filename);
//		root->WriteFileTo(outfile);
//		outfile.close();
//		outfile.flush();
//	}
//}

void Element::WriteFileTo(std::ostream& o, int depth)
{
	if (Name != "")
	{
		for (int i = 0; i < depth; i++) { o << '\t'; }
		o << '<' << Name;
		for (unsigned int i = 0; i < Parameters.size(); i++)
		{
			o << ' ' << Parameters[i].Name << "=" << '\"' << Parameters[i].Value << '\"';
		}
		if (Name[0] == '?') { o << "?"; }
		o << '>';
		o << endl;
	}

	if (Nodes.size())
	{
		for (unsigned int i = 0; i < Nodes.size(); i++)
		{
			Nodes[i]->WriteFileTo(o, depth + ((Name != "" ) ? 1 : 0));
		}
	}
	else if (Subtext != "")
	{
		o << Subtext;
		o << endl;
	}

	if (Name != "" && Name[0] != '?')
	{
		for (int i = 0; i < depth; i++) { o << '\t'; }
		o << "</" << Name << ">" << endl;
	}
}
